--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
meshchatx/src/backend/rrc/protocol.py 159b2358f1913abb25eafcc3e23b1777f2ed9f15 (159b2358) Text, 7.40 KB
T8b949e# SPDX-License-Identifier: 0BSD
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
T8b949e"""Wire protocol primitives for Reticulum Relay Chat.
This module mirrors the constants and envelope structure defined by the RRC
specification (https://rrc.kc1awv.net/) and the reference rrcd implementation.
It deliberately avoids importing Reticulum so the encoding rules, envelope
construction, and hub notice parsers can be exercised by unit tests without a
running mesh.
"""
Tff7b72import T7ee787base64
Tff7b72import T7ee787os
Tff7b72import T7ee787re
Tff7b72import T7ee787time
Tff7b72import T7ee787cbor2
Te6edf3RRC_VERSION Tff7b72= T79c0ff1
Te6edf3K_V Tff7b72= T79c0ff0
Te6edf3K_T Tff7b72= T79c0ff1
Te6edf3K_ID Tff7b72= T79c0ff2
Te6edf3K_TS Tff7b72= T79c0ff3
Te6edf3K_SRC Tff7b72= T79c0ff4
Te6edf3K_ROOM Tff7b72= T79c0ff5
Te6edf3K_BODY Tff7b72= T79c0ff6
Te6edf3K_NICK Tff7b72= T79c0ff7
Te6edf3T_HELLO Tff7b72= T79c0ff1
Te6edf3T_WELCOME Tff7b72= T79c0ff2
Te6edf3T_JOIN Tff7b72= T79c0ff10
Te6edf3T_JOINED Tff7b72= T79c0ff11
Te6edf3T_PART Tff7b72= T79c0ff12
Te6edf3T_PARTED Tff7b72= T79c0ff13
Te6edf3T_MSG Tff7b72= T79c0ff20
Te6edf3T_NOTICE Tff7b72= T79c0ff21
Te6edf3T_ACTION Tff7b72= T79c0ff22
Te6edf3T_PING Tff7b72= T79c0ff30
Te6edf3T_PONG Tff7b72= T79c0ff31
Te6edf3T_ERROR Tff7b72= T79c0ff40
Te6edf3T_RESOURCE_ENVELOPE Tff7b72= T79c0ff50
Te6edf3B_HELLO_NAME Tff7b72= T79c0ff0
Te6edf3B_HELLO_VER Tff7b72= T79c0ff1
Te6edf3B_HELLO_CAPS Tff7b72= T79c0ff2
Te6edf3B_WELCOME_HUB Tff7b72= T79c0ff0
Te6edf3B_WELCOME_VER Tff7b72= T79c0ff1
Te6edf3B_WELCOME_CAPS Tff7b72= T79c0ff2
Te6edf3B_WELCOME_LIMITS Tff7b72= T79c0ff3
Te6edf3L_MAX_NICK_BYTES Tff7b72= T79c0ff0
Te6edf3L_MAX_ROOM_NAME_BYTES Tff7b72= T79c0ff1
Te6edf3L_MAX_MSG_BODY_BYTES Tff7b72= T79c0ff2
Te6edf3L_MAX_ROOMS_PER_SESSION Tff7b72= T79c0ff3
Te6edf3L_RATE_LIMIT_MSGS_PER_MINUTE Tff7b72= T79c0ff4
Te6edf3CAP_RESOURCE_ENVELOPE Tff7b72= T79c0ff0
Te6edf3CAP_ACTION Tff7b72= T79c0ff1
Te6edf3B_RES_ID Tff7b72= T79c0ff0
Te6edf3B_RES_KIND Tff7b72= T79c0ff1
Te6edf3B_RES_SIZE Tff7b72= T79c0ff2
Te6edf3B_RES_SHA256 Tff7b72= T79c0ff3
Te6edf3B_RES_ENCODING Tff7b72= T79c0ff4
Te6edf3RES_KIND_NOTICE Tff7b72= Ta5d6ff"Ta5d6ffnoticeTa5d6ff"
Te6edf3RES_KIND_MOTD Tff7b72= Ta5d6ff"Ta5d6ffmotdTa5d6ff"
Te6edf3RES_KIND_BLOB Tff7b72= Ta5d6ff"Ta5d6ffblobTa5d6ff"
Te6edf3HUB_HASH_BYTES Tff7b72= T79c0ff16
Te6edf3DEFAULT_DEST_NAME Tff7b72= Ta5d6ff"Ta5d6ffrrc.hubTa5d6ff"
Te6edf3DEFAULT_MAX_NICK_BYTES Tff7b72= T79c0ff32
Te6edf3DEFAULT_MAX_ROOM_BYTES Tff7b72= T79c0ff64
Te6edf3DEFAULT_MAX_MSG_BYTES Tff7b72= T79c0ff350
Te6edf3DEFAULT_MAX_ROOMS Tff7b72= T79c0ff32
Te6edf3DEFAULT_RATE_PER_MINUTE Tff7b72= T79c0ff240
Te6edf3HELLO_CLIENT_NAME Tff7b72= Ta5d6ff"Ta5d6ffmeshchatxTa5d6ff"
Te6edf3HELLO_CLIENT_VERSION Tff7b72= Ta5d6ff"Ta5d6ff1Ta5d6ff"
Tff7b72def Td2a8ffencodeTb4b4b4(Te6edf3objTb4b4b4)Tb4b4b4:
T8b949e"""Encode an object into canonical CBOR bytes for the wire."""
Tff7b72return Te6edf3cbor2Tff7b72.Td2a8ffdumpsTb4b4b4(Te6edf3objTb4b4b4, Te6edf3canonicalTff7b72=Tff7b72TrueTb4b4b4)
Tff7b72def Td2a8ffdecodeTb4b4b4(Te6edf3dataTb4b4b4)Tb4b4b4:
T8b949e"""Decode CBOR bytes into a Python object."""
Tff7b72return Te6edf3cbor2Tff7b72.Td2a8ffloadsTb4b4b4(Te6edf3dataTb4b4b4)
Tff7b72def Td2a8ffloadTb4b4b4(Te6edf3fpTb4b4b4)Tb4b4b4:
T8b949e"""Read a single CBOR value from a stream, raising ``EOFError`` at the end."""
Tff7b72tryTb4b4b4:
Tff7b72return Te6edf3cbor2Tff7b72.Td2a8ffloadTb4b4b4(Te6edf3fpTb4b4b4)
Tff7b72except Te6edf3cbor2Tff7b72.Td2a8ffCBORDecodeEOF Tff7b72as Te6edf3excTb4b4b4:
Tff7b72raise Tf85149EOFError Tff7b72from T7ee787exc
Tff7b72def Td2a8ffnow_msTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Return the current time in milliseconds."""
Tff7b72return Tffa657intTb4b4b4(Te6edf3timeTff7b72.Td2a8fftimeTb4b4b4(Tb4b4b4) Tff7b72* T79c0ff1000Tb4b4b4)
Tff7b72def Td2a8ffnew_msg_idTb4b4b4(Tb4b4b4)Tb4b4b4:
T8b949e"""Generate a random 8-byte message identifier."""
Tff7b72return Te6edf3osTff7b72.Td2a8ffurandomTb4b4b4(T79c0ff8Tb4b4b4)
Te6edf3_MENTION_RE_CACHE Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72def Td2a8ffmention_reTb4b4b4(Te6edf3nickTb4b4b4)Tb4b4b4:
T8b949e"""Return a compiled regex matching ``@nick`` mentions, or ``None``."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3nickTb4b4b4, Tffa657strTb4b4b4) Tff7b72or Tff7b72not Te6edf3nickTb4b4b4:
Tff7b72return Tff7b72None
Te6edf3pat Tff7b72= Te6edf3_MENTION_RE_CACHETff7b72.Td2a8ffgetTb4b4b4(Te6edf3nickTb4b4b4)
Tff7b72if Te6edf3pat Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3pat Tff7b72= Te6edf3reTff7b72.Td2a8ffcompileTb4b4b4(
Ta5d6ffrTa5d6ff"Ta5d6ff(?<![A-Za-z0-9_])@Ta5d6ff" Tff7b72+ Te6edf3reTff7b72.Td2a8ffescapeTb4b4b4(Te6edf3nickTb4b4b4) Tff7b72+ Ta5d6ffrTa5d6ff"Ta5d6ff(?![A-Za-z0-9_])Ta5d6ff"Tb4b4b4,
Te6edf3reTff7b72.Td2a8ffIGNORECASETb4b4b4,
Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3_MENTION_RE_CACHETb4b4b4) Tff7b72> T79c0ff32Tb4b4b4:
Te6edf3_MENTION_RE_CACHETff7b72.Td2a8ffclearTb4b4b4(Tb4b4b4)
Te6edf3_MENTION_RE_CACHETb4b4b4[Te6edf3nickTb4b4b4] Tff7b72= Te6edf3pat
Tff7b72return Te6edf3pat
Tff7b72def Td2a8fftext_mentionsTb4b4b4(Te6edf3textTb4b4b4, Te6edf3nickTb4b4b4)Tb4b4b4:
T8b949e"""Return ``True`` when ``text`` mentions ``nick``."""
Te6edf3pat Tff7b72= Te6edf3mention_reTb4b4b4(Te6edf3nickTb4b4b4)
Tff7b72return Tffa657boolTb4b4b4(Te6edf3pat Tff7b72is Tff7b72not Tff7b72None Tff7b72and Tffa657isinstanceTb4b4b4(Te6edf3textTb4b4b4, Tffa657strTb4b4b4) Tff7b72and Te6edf3patTff7b72.Td2a8ffsearchTb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4)
Tff7b72def Td2a8ffmake_envelopeTb4b4b4(Te6edf3msg_typeTb4b4b4, Te6edf3srcTb4b4b4, Te6edf3roomTff7b72=Tff7b72NoneTb4b4b4, Te6edf3bodyTff7b72=Tff7b72NoneTb4b4b4, Te6edf3nickTff7b72=Tff7b72NoneTb4b4b4, Te6edf3midTff7b72=Tff7b72NoneTb4b4b4, Te6edf3tsTff7b72=Tff7b72NoneTb4b4b4)Tb4b4b4:
T8b949e"""Build an RRC envelope dict ready for CBOR encoding."""
Te6edf3env Tff7b72= Tb4b4b4{
Te6edf3K_VTb4b4b4: Te6edf3RRC_VERSIONTb4b4b4,
Te6edf3K_TTb4b4b4: Tffa657intTb4b4b4(Te6edf3msg_typeTb4b4b4)Tb4b4b4,
Te6edf3K_IDTb4b4b4: Te6edf3mid Tff7b72or Te6edf3new_msg_idTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3K_TSTb4b4b4: Te6edf3ts Tff7b72or Te6edf3now_msTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3K_SRCTb4b4b4: Te6edf3srcTb4b4b4,
Tb4b4b4}
Tff7b72if Te6edf3room Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3envTb4b4b4[Te6edf3K_ROOMTb4b4b4] Tff7b72= Te6edf3room
Tff7b72if Te6edf3body Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3envTb4b4b4[Te6edf3K_BODYTb4b4b4] Tff7b72= Te6edf3body
Tff7b72if Te6edf3nick Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3nick Tff7b72!= Ta5d6ff"Ta5d6ff"Tb4b4b4:
Te6edf3envTb4b4b4[Te6edf3K_NICKTb4b4b4] Tff7b72= Te6edf3nick
Tff7b72return Te6edf3env
Tff7b72def Td2a8ffdisplay_name_from_hub_app_dataTb4b4b4(Te6edf3app_data_b64Tb4b4b4)Tb4b4b4:
T8b949e"""Return the hub name from a base64-encoded RRC announce, or ``None``.
Hosted hubs announce CBOR ``app_data`` of the form
``{"proto": "rrc", "v": 1, "hub": name}``.
"""
Tff7b72if Tff7b72not Te6edf3app_data_b64Tb4b4b4:
Tff7b72return Tff7b72None
Tff7b72tryTb4b4b4:
Te6edf3raw Tff7b72= Te6edf3base64Tff7b72.Td2a8ffb64decodeTb4b4b4(Te6edf3app_data_b64Tb4b4b4)
Te6edf3obj Tff7b72= Te6edf3decodeTb4b4b4(Te6edf3rawTb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3objTb4b4b4, Tffa657dictTb4b4b4)Tb4b4b4:
Te6edf3name Tff7b72= Te6edf3objTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffhubTa5d6ff"Tb4b4b4)
Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3nameTb4b4b4, Tffa657strTb4b4b4) Tff7b72and Te6edf3nameTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3nameTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72return Tff7b72None
Tff7b72def Td2a8ffnormalize_nickTb4b4b4(Te6edf3nickTb4b4b4, Te6edf3max_bytesTff7b72=Te6edf3DEFAULT_MAX_NICK_BYTESTb4b4b4)Tb4b4b4:
T8b949e"""Normalize a nickname, returning ``None`` when empty or invalid."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3nickTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3n Tff7b72= Ta5d6ff"Ta5d6ff Ta5d6ff"Tff7b72.Td2a8ffjoinTb4b4b4(Te6edf3nickTff7b72.Td2a8ffsplitTb4b4b4(Tb4b4b4)Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3nTb4b4b4:
Tff7b72return Tff7b72None
Te6edf3encoded Tff7b72= Te6edf3nTff7b72.Td2a8ffencodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4, Te6edf3errorsTff7b72=Ta5d6ff"Ta5d6ffreplaceTa5d6ff"Tb4b4b4)
Tff7b72if Tffa657lenTb4b4b4(Te6edf3encodedTb4b4b4) Tff7b72> Te6edf3max_bytesTb4b4b4:
Te6edf3n Tff7b72= Te6edf3encodedTb4b4b4[Tb4b4b4:Te6edf3max_bytesTb4b4b4]Tff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4, Te6edf3errorsTff7b72=Ta5d6ff"Ta5d6ffignoreTa5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3nTb4b4b4:
Tff7b72return Tff7b72None
Tff7b72return Te6edf3n
Tff7b72def Td2a8ffnormalize_roomTb4b4b4(Te6edf3roomTb4b4b4)Tb4b4b4:
T8b949e"""Normalize a room name to its canonical lowercase form."""
Te6edf3r Tff7b72= Tb4b4b4(Te6edf3room Tff7b72or Ta5d6ff"Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3rTb4b4b4:
Te6edf3msg Tff7b72= Ta5d6ff"Ta5d6ffroom must not be emptyTa5d6ff"
Tff7b72raise Tf85149ValueErrorTb4b4b4(Te6edf3msgTb4b4b4)
Tff7b72return Te6edf3r
T8b949e# greedy .+ intentionally captures nicks containing parens like
T8b949e# "user (alt) (deadbeefcafe)"
Te6edf3_WHO_ENTRY_RE Tff7b72= Te6edf3reTff7b72.Td2a8ffcompileTb4b4b4(
Ta5d6ffrTa5d6ff"Ta5d6ff(?:^|,Ta5d6ff\Ta5d6ffs)Ta5d6ff"
Ta5d6ffrTa5d6ff"Ta5d6ff(?:(?P<bh>[0-9a-fA-F]Tffd700{32}Ta5d6ff)|(?P<nick>.+?)Ta5d6ff\Ta5d6ffsTa5d6ff\Ta5d6ff((?P<np>[0-9a-fA-F]Tffd700{12}Ta5d6ff)Ta5d6ff\Ta5d6ff))Ta5d6ff"
Ta5d6ffrTa5d6ff"Ta5d6ff(?=,Ta5d6ff\Ta5d6ffs|$)Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffparse_who_noticeTb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4:
T8b949e"""Parse a hub ``/who`` notice into ``(room, [(nick, hex), ...])``.
Nicked users carry only a 12-hex prefix of their identity hash, while
un-nicked users appear as their full hex hash. Returns ``None`` when the
notice is not a ``/who`` response.
"""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3textTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3prefix Tff7b72= Ta5d6ff"Ta5d6ffmembers in Ta5d6ff"
Tff7b72if Tff7b72not Te6edf3textTff7b72.Td2a8ffstartswithTb4b4b4(Te6edf3prefixTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3sep_idx Tff7b72= Te6edf3textTff7b72.Td2a8fffindTb4b4b4(Ta5d6ff"Ta5d6ff: Ta5d6ff"Tb4b4b4, Tffa657lenTb4b4b4(Te6edf3prefixTb4b4b4)Tb4b4b4)
Tff7b72if Te6edf3sep_idx Tff7b72< T79c0ff0Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3room Tff7b72= Te6edf3textTb4b4b4[Tffa657lenTb4b4b4(Te6edf3prefixTb4b4b4) Tb4b4b4: Te6edf3sep_idxTb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3roomTb4b4b4:
Tff7b72return Tff7b72None
Te6edf3body Tff7b72= Te6edf3textTb4b4b4[Te6edf3sep_idx Tff7b72+ T79c0ff2 Tb4b4b4:Tb4b4b4]Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3entries Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72if Te6edf3body Tff7b72and Te6edf3body Tff7b72!= Ta5d6ff"Ta5d6ff(none)Ta5d6ff"Tb4b4b4:
Tff7b72for Te6edf3m Tff7b72in Te6edf3_WHO_ENTRY_RETff7b72.Td2a8fffinditerTb4b4b4(Te6edf3bodyTb4b4b4)Tb4b4b4:
Tff7b72if Te6edf3mTff7b72.Td2a8ffgroupTb4b4b4(Ta5d6ff"Ta5d6ffnickTa5d6ff"Tb4b4b4) Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3entriesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Te6edf3mTff7b72.Td2a8ffgroupTb4b4b4(Ta5d6ff"Ta5d6ffnickTa5d6ff"Tb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3mTff7b72.Td2a8ffgroupTb4b4b4(Ta5d6ff"Ta5d6ffnpTa5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72elif Te6edf3mTff7b72.Td2a8ffgroupTb4b4b4(Ta5d6ff"Ta5d6ffbhTa5d6ff"Tb4b4b4) Tff7b72is Tff7b72not Tff7b72NoneTb4b4b4:
Te6edf3entriesTff7b72.Td2a8ffappendTb4b4b4(Tb4b4b4(Tff7b72NoneTb4b4b4, Te6edf3mTff7b72.Td2a8ffgroupTb4b4b4(Ta5d6ff"Ta5d6ffbhTa5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72return Tb4b4b4(Te6edf3roomTb4b4b4, Te6edf3entriesTb4b4b4)
Tff7b72def Td2a8ffparse_room_list_noticeTb4b4b4(Te6edf3textTb4b4b4)Tb4b4b4:
T8b949e"""Parse a hub ``/list`` notice into ``{room: topic_or_None}`` or ``None``."""
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3textTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3stripped Tff7b72= Te6edf3textTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3stripped Tff7b72== Ta5d6ff"Ta5d6ffNo public rooms registeredTa5d6ff"Tb4b4b4:
Tff7b72return Tb4b4b4{Tb4b4b4}
Te6edf3lines Tff7b72= Te6edf3textTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Tffea00\nTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3lines Tff7b72or Tff7b72not Te6edf3linesTb4b4b4[T79c0ff0Tb4b4b4]Tff7b72.Td2a8fflstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8ffstartswithTb4b4b4(Ta5d6ff"Ta5d6ffRegistered public roomsTa5d6ff"Tb4b4b4)Tb4b4b4:
Tff7b72return Tff7b72None
Te6edf3rooms Tff7b72= Tb4b4b4{Tb4b4b4}
Tff7b72for Te6edf3line Tff7b72in Te6edf3linesTb4b4b4[T79c0ff1Tb4b4b4:Tb4b4b4]Tb4b4b4:
Te6edf3s Tff7b72= Te6edf3lineTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3sTb4b4b4:
Tff7b72continue
Tff7b72if Ta5d6ff"Ta5d6ff - Ta5d6ff" Tff7b72in Te6edf3sTb4b4b4:
Te6edf3nameTb4b4b4, Te6edf3topic Tff7b72= Te6edf3sTff7b72.Td2a8ffsplitTb4b4b4(Ta5d6ff"Ta5d6ff - Ta5d6ff"Tb4b4b4, T79c0ff1Tb4b4b4)
Te6edf3roomsTb4b4b4[Te6edf3nameTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tb4b4b4] Tff7b72= Te6edf3topicTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4) Tff7b72or Tff7b72None
Tff7b72elseTb4b4b4:
Te6edf3roomsTb4b4b4[Te6edf3sTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)Tff7b72.Td2a8fflstripTb4b4b4(Ta5d6ff"Ta5d6ff#Ta5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4)Tb4b4b4] Tff7b72= Tff7b72None
Tff7b72return Te6edf3rooms
Tff7b72class T56d364RRCMessageTb4b4b4:
T8b949e"""A single chat event (message, action, notice, or system line)."""
Tff7b72def Tff7b72__init__Tb4b4b4(Tff7b72selfTb4b4b4, Te6edf3kindTb4b4b4, Te6edf3roomTb4b4b4, Te6edf3srcTb4b4b4, Te6edf3nickTb4b4b4, Te6edf3textTb4b4b4, Te6edf3tsTb4b4b4)Tb4b4b4:
Tff7b72selfTff7b72.Td2a8ffkind Tff7b72= Te6edf3kind
Tff7b72selfTff7b72.Td2a8ffroom Tff7b72= Te6edf3room
Tff7b72selfTff7b72.Td2a8ffsrc Tff7b72= Te6edf3src
Tff7b72selfTff7b72.Td2a8ffnick Tff7b72= Te6edf3nick
Tff7b72selfTff7b72.Td2a8fftext Tff7b72= Te6edf3text
Tff7b72selfTff7b72.Td2a8ffts Tff7b72= Te6edf3ts
Tff7b72selfTff7b72.Td2a8ffmention Tff7b72= Tff7b72False
Tff7b72def Td2a8ffto_dictTb4b4b4(Tff7b72selfTb4b4b4)Tb4b4b4:
T8b949e"""Return a JSON-serializable representation of the message."""
Te6edf3src Tff7b72= Tff7b72selfTff7b72.Td2a8ffsrc
Tff7b72return Tb4b4b4{
Ta5d6ff"Ta5d6ffkindTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffkindTb4b4b4,
Ta5d6ff"Ta5d6ffroomTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffroomTb4b4b4,
Ta5d6ff"Ta5d6ffsrcTa5d6ff"Tb4b4b4: Te6edf3srcTff7b72.Td2a8ffhexTb4b4b4(Tb4b4b4) Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3srcTb4b4b4, Tb4b4b4(Tffa657bytesTb4b4b4, Tffa657bytearrayTb4b4b4)Tb4b4b4) Tff7b72else Tff7b72NoneTb4b4b4,
Ta5d6ff"Ta5d6ffnickTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8ffnickTb4b4b4,
Ta5d6ff"Ta5d6fftextTa5d6ff"Tb4b4b4: Tff7b72selfTff7b72.Td2a8fftext Tff7b72if Tffa657isinstanceTb4b4b4(Tff7b72selfTff7b72.Td2a8fftextTb4b4b4, Tffa657strTb4b4b4) Tff7b72else Ta5d6ff"Ta5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6fftsTa5d6ff"Tb4b4b4: Tffa657intTb4b4b4(Tff7b72selfTff7b72.Td2a8fftsTb4b4b4) Tff7b72if Tffa657isinstanceTb4b4b4(Tff7b72selfTff7b72.Td2a8fftsTb4b4b4, Tffa657intTb4b4b4) Tff7b72else T79c0ff0Tb4b4b4,
Ta5d6ff"Ta5d6ffmentionTa5d6ff"Tb4b4b4: Tffa657boolTb4b4b4(Tff7b72selfTff7b72.Td2a8ffmentionTb4b4b4)Tb4b4b4,
Tb4b4b4}
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────